package parser

import (
	"bufio"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"unsafe"

	"gitee.com/u-language/u-language/ucom/ast2"
	"gitee.com/u-language/u-language/ucom/check3"
	"gitee.com/u-language/u-language/ucom/errcode"
	"gitee.com/u-language/u-language/ucom/format"
	"gitee.com/u-language/u-language/ucom/internal/time"
	"gitee.com/u-language/u-language/ucom/internal/utils"
	"gitee.com/u-language/u-language/ucom/lex2"
)

// ParserFileBuildMode2 从一个代码文件创建ast树
//   - path是文件路径
//   - errctx是错误处理上下文
//   - IsCheck是控制是否进行语义检查
//   - Thread是控制是否并发
func ParserFileBuildMode2(path string, errctx *errcode.ErrCtx, IsCheck bool, Thread bool) (*ast2.Tree, error) {
	file, size, buf, err := openfileAndInfo(path) //获取文件描述符，文件大小，测试时还有全部内容
	if err != nil {
		return nil, err
	}
	var r *bufio.Reader
	if test { //测试时，这些是为了在Benchmark时获得的数据是从去除换行符到抽象语法树生成完毕，不包括打开文件等操作
		str := *(*string)(unsafe.Pointer(&buf))
		sbuf := strbufpool.Get().(*strings.Reader)
		defer strbufpool.Put(sbuf)
		sbuf.Reset(str)
		r = bufreadpool.Get().(*bufio.Reader)
		defer bufreadpool.Put(r)
		r.Reset(sbuf)
	} else {
		r = bufio.NewReaderSize(file, int(size))
	}
	var t time.Time
	try_new_print_LexTime(&t)
	FT := lex2.NewFileToken(path, r, errctx, false) //进行词法分析
	try_Print_Time(&t)
	if errctx.Errbol() { //如果词法分析有错误
		return nil, format.ErrorAfterLex
	}
	try_new_print_AstTime(&t)
	tree := ast2.NewTree(FT, path, errctx, Thread) //创建抽象语法树
	err = loadAllImport2(filepath.Dir(path), tree.Sbt, *tree.ImportPackage, Thread, errctx, tree.ImporPath)
	if err != nil {
		return nil, err
	}
	try_Print_Time(&t)
	try_print(tree)
	if errctx.Errbol() { //如果语法分析有错误
		return nil, nil
	}
	if IsCheck { //如果要进行语义检查
		try_new_print_check(&t)
		check3.CheckTree(tree)
		try_Print_Time(&t)
	}
	return tree, nil
}

// ParserPackageBuildMode2 转换一个目录下所有扩展名为.u的源代码文件为抽象语法树
func ParserPackageBuildMode2(dir string, thread bool, errctx *errcode.ErrCtx, sbt *ast2.Sbt, ImportPackage map[string]*ast2.Package) (*ast2.Package, error) {
	paths, err := utils.FindU(dir)
	if err != nil {
		return nil, err
	}
	if len(paths) == 0 {
		return nil, fmt.Errorf("%s目录%w", dir, NoUFileErr)
	}
	p := ast2.NewPackage(dir, thread, errctx, sbt, ImportPackage)
	var wg = utils.NewWaitGroup(thread)
	var t time.Time
	var result = make(chan error, len(paths))
	for _, v := range paths { //将所有源文件进行词法分析后加入包中
		v := v
		wg.Go(func() {
			defer wg.Done()
			file, size, buf, err := openfileAndInfo(v)
			if err != nil {
				result <- fmt.Errorf("path=%s \t err=%w", v, err)
				return
			}
			var r *bufio.Reader
			if !test || notest { //如果非测试
				r = bufio.NewReaderSize(file, int(size))
			} else {
				//测试时，这些是为了在Benchmark时获得的数据是从去除换行符到抽象语法树生成完毕，不包括打开文件等操作
				str := *(*string)(unsafe.Pointer(&buf))
				sbuf := strbufpool.Get().(*strings.Reader)
				defer strbufpool.Put(sbuf)
				sbuf.Reset(str)
				r = bufreadpool.Get().(*bufio.Reader)
				defer bufreadpool.Put(r)
				r.Reset(sbuf)
			}
			if !test || notest { //如果非测试
				defer file.Close()
			}
			FT := lex2.NewFileToken(v, r, errctx, false) //进行词法分析
			if errctx.Errbol() {                         //如果词法分析有错误·
				return
			}
			p.AddFile(FT)
		})
	}
	wg.Wait()
	select {
	case err = <-result:
		if err != nil {
			return nil, err
		}
	default:
	}
	err = loadAllImport2(dir, p.Sbt, p.ImportPackage, thread, errctx, p.ImporPath)
	if err != nil {
		return nil, fmt.Errorf("%s中的%w", p.PackageName, err)
	}
	try_print(p)
	//进行语义检查
	try_new_print_check(&t)
	check3.CheckPackage(p, errctx, thread)
	try_Print_Time(&t)
	return p, nil
}

// loadAllImport2 获取导入包的ast
//   - dir是目录
//   - ImportPackage 是自己及依赖导入的包
//   - sbt 是符号表
//   - thread 控制是否并发
//   - errctx 是错误处理上下文
//   - ImportPath 是所有的导入路径
func loadAllImport2(dir string, sbt *ast2.Sbt, ImportPackage map[string]*ast2.Package, thread bool, errctx *errcode.ErrCtx, ImporPath *ast2.Sbt) error {
	var v []string
	ImporPath.Range(func(key string, _ fmt.Stringer) bool {
		v = append(v, key)
		return true
	})
	var err error
	for _, importPath := range v { //获取每个被导入的包
		importPath = importPath[1 : len(importPath)-1] //去除字符串左右的"
		importPath, err = importPathToAbs(dir, importPath)
		if err != nil {
			return err
		}
		p, ok := ImportPackage[importPath]
		if ok { //如果已经被导入
			continue
		}
		p, err = loadImport2(importPath, thread, errctx, sbt, ImportPackage)
		if err != nil {
			return err
		}
		ImportPackage[importPath] = p
		if err := sbt.AddPackage(p); err != errcode.NoErr {
			//TODO:更好的报错
			errcode.Panic(importPath, 1, nil, err)
		}
		err = loadAllImport2(importPath, p.Sbt, ImportPackage, thread, errctx, p.ImporPath) //导入被导入包导入的包
		if err != nil {
			return fmt.Errorf("%s中的%w", p.PackageName, err)
		}
	}
	return nil
}

func loadImport2(path string, thread bool, errctx *errcode.ErrCtx, sbt *ast2.Sbt, ImportPackage map[string]*ast2.Package) (p *ast2.Package, err error) {
	p, err = ParserPackageBuildMode2(path, thread, errctx, sbt, ImportPackage)
	if errors.Is(err, os.ErrNotExist) {
		return nil, fmt.Errorf("%w %s", PathNotFound, path)
	}
	return
}
